
import unittest
import helper.person as person

def closure_sample(start = 0):
    # here must use list, because list itself will not be written
    # only access (read) its element, then will not create new identifier in local scope
    count = [start]
    
    def increment():
        count[0] += 1
        return count[0]
    
    return increment
    

class TestFunction(unittest.TestCase):
    def test_keyword_arguments(self):
        
        def function(start=0,end=10):
            return start,end
        
        self.assertEqual((0,10),function())
        self.assertEqual(("stasi","cheka"),function(end="cheka",start="stasi"))
        self.assertEqual((0,"mss"),function(end="mss"))
        self.assertEqual(("kgb",10),function(start="kgb"))
        
    def test_keyword_argument2(self):
        """
        using keyword argument with function having no default argument
        """
        aperson = person.Person(name="cheka",ssn=1)
        self.assertEqual(1,aperson._ssn)
        self.assertEqual("cheka",aperson._name)
        
        self.assertRaises(TypeError, lambda : person.Person(ssn = 2) )
        self.assertRaises(TypeError, lambda : person.Person(ssn = 2,nokey = "no such key") )
        
    def test_delegate(self):
        """
        test the "Delegate" feature in Python, that passes function as argument
        """
        # ====================== define isloated function
        def double_number(value):
            return value * 2
        
        # ====================== define instance methods
        class Multiplier(object):
            
            def __init__(self,times):
                self.__times = times
                
            def set_times(self,times):
                self.__times = times
            
            def multiple(self,value):
                return value * self.__times
            
        # ====================== define delegate consumer
        def transform_number(value,funclet):
            return funclet(value)
        
        # ====================== test
        self.assertEqual(4,transform_number(2,double_number))
        
        mulobj = Multiplier(5)
        self.assertEqual(90,transform_number(18,mulobj.multiple))
        
        mulobj.set_times(10)
        self.assertEqual(50,transform_number(5,mulobj.multiple)) 
        
        
    def test_passing_parameter(self):
        """
        this testcase checks that parameter passing is always "address passing" in python
        """
        def simple_function(alist):
            alist = [] # redirect to a totally new space
            alist.append(1)
            return alist
        
        empty_list = []
        returned_list = simple_function(empty_list)
        
        self.assertEqual([],empty_list)
        self.assertEqual([1],returned_list)
        

class TestGroupParameters(unittest.TestCase):
    # ---------------------- for test list arguments
    def _sum(self,*args):
        """
        in the function body, args can be used as a tuple
        when used inside method body, "*" stands for group, putting individual argument into a list
        """
        size = len(args)
        
        sum = args[0]
        for index in range(1,size):
            sum += args[index]
        return sum
    
    def test_tuple_parameter(self):
        self.assertEqual(6,self._sum(1,2,3))
        self.assertEqual("chekakgbstasi",self._sum("cheka","kgb","stasi"))
        
    # ---------------------- for test dictionary arguments
    def _split_keys_values(self,keys,values,**kwargs):
        """
        when used inside method body, "**" works as grouper, collect all individual arguments into a collection
        """
        del keys[:]
        del values[:]
        
        for kv in kwargs.items():
            keys.append(kv[0])
            values.append(kv[1])
            
        keys.sort()
        values.sort()
    
    def test_dict_parameters(self):
        # set some initial values, to test the effect of clearing inside the method
        keys = range(3)
        values = range(5)
        
        # chekanote: to pass as "keyword argument", passing as "key=value" format
        # and in the method body, all key-word arguments will be grouped into a dictionary
        # where the key is like "key"
        self._split_keys_values(keys,values,stasi = 100,cheka=10,kgb=2,cia=3)
        
        self.assertEqual(["cheka","cia","kgb","stasi"],keys)
        self.assertEqual([2,3,10,100],values)
        
    def test_split_arguments(self):
        """
        when used outside method to invoke function with grouped parameter
        "*" or "**" works as spliter, split a collection into individual segments and pass into that method with grouped parameter
        """
        # ----------- split list 
        # ----------- without, (1,2,3) is passed as a single element, then inside method, args is a collection with only one argument
        self.assertEqual((1,2,3),self._sum((1,2,3)))
        self.assertEqual(18,self._sum(*(5,6,7)))
        
        # ----------- split dictionary
        keys = [1]
        values = [None]
        
        # chekanote: below codes throw TypeError exception, due to "keyword can only be string" 
        # self._split_keys_values(keys,values,**{2:"stasi",1:"cheka"})
        self._split_keys_values(keys,values,**{"stasi":2,"cheka":1})
        self.assertEqual([1,2],values)
        self.assertEqual(["cheka","stasi"],keys)


    def test_default_extend_args(self):
        """
        test the case that default arguments mixed with tuple arguments
        """
        def _method(arg1,defarg="default",*argtuple,**kwargs):
            return {"arg1":arg1,"default_arg":defarg,"arg_tuple":argtuple,"arg_dict":kwargs}
        
        self.assertEqual({"arg1":100,"arg_dict":{},"arg_tuple":(),"default_arg":"default"},_method(100))
        self.assertEqual({"arg1":101,"arg_dict":{},"arg_tuple":(),"default_arg":"newvalue"},_method(101,"newvalue"))
        
        # only when we have already filled out the two non-tuple argument, left argument will be collected into tuple
        self.assertEqual({"arg1":102,"arg_dict":{},"arg_tuple":(1,2),"default_arg":"newvalue2"},_method(102,"newvalue2",1,2))
        
        # keyword arguments are always the last
        self.assertEqual({"arg1":103,"arg_dict":{"x":99},"arg_tuple":(),"default_arg":"cheka"},
                         _method(arg1=103,defarg="cheka",x=99))

class TestClosure(unittest.TestCase):
    def test_new_name_created(self):
        """
        chekanote: one big difference about the closure in Python and C# is that
        there is possibility to create a totally new same inside the closure, but happen to have same literal name with outside one
        so though it appears to be "same variable", but in fact refer to different object
        
        but in C#, there is no such problem, because in C#, you have to declare some variables with its type and name
        so if you uses a name without declaration, then at compile time, the compiler will transfer that codes into a closure
        it even happen before runtime
        """
        value = 0
        
        def closure(newvalue):
            # create a new name inside the namespace
            # so will not change the outer name
            value = newvalue 
            return value
            
        self.assertEqual( 100, closure(100) )
        self.assertEqual(0,value)
        
    def test_outer_variable(self):
        alist = []
        
        def closure(value):
            alist.append(value)
            
        count = 5
        for index in range(count):
            closure(index)
            
        self.assertEqual(range(count),alist)
            



    def test_closure_sample(self):
        # this adder1 takes the scope of "closure_sample()"
        adder1 = closure_sample()
        self.assertEqual(1,adder1())
        self.assertEqual(2,adder1())
        
        # this adder2 takes the scope of "closure_sample(6)"
        # a totally different namespace
        adder2 = closure_sample(6)
        self.assertEqual(7,adder2())
        self.assertEqual(8,adder2())
        self.assertEqual(3,adder1())

class TestLambda(unittest.TestCase):
    def test_lambda_defargs(self):
        func = lambda x,y=2 : x+y
        self.assertEqual(5,func(3))
        
    def test_lambda_varilen_args(self):
        func = lambda *argtuple : argtuple
        self.assertEqual((2,3),func(2,3))
        
    def test_lambda_def_arg(self):
        x = 10
        y = 5
        func = lambda z=y : x+z
        self.assertEqual(15,func())
        
        # chekanote: the default argument is fixed when function is defined
        # default argument will not be changed later
        y = 8
        self.assertEqual(15,func())

# ***************************************************************************************** #        
if __name__ == "__main__":
    unittest.main()